// Copyright (C) 2022 即时通讯网(52im.net) & Jack Jiang.
// The RainbowChat Project. All rights reserved.
//
// 【本产品为著作权产品，合法授权后请放心使用，禁止外传！】
// 【本次授权给：<广西木子科技有限公司>，授权编号：<NT220402151538>，代码指纹：<A.648883738.885>，技术对接人微信：<ID: Lingmuziyi>】
// 【授权寄送：<收件：李先生、地址：南宁市科园西十路11号国电智能大厦1101F、电话：17736659550、邮箱：yingshashou@vip.qq.com>】
//
// 【本系列产品在国家版权局的著作权登记信息如下】：
// 1）国家版权局登记名(简称)和权证号：RainbowChat    （证书号：软著登字第1220494号、登记号：2016SR041877）
// 2）国家版权局登记名(简称)和权证号：RainbowChat-Web（证书号：软著登字第3743440号、登记号：2019SR0322683）
// 3）国家版权局登记名(简称)和权证号：RainbowAV      （证书号：软著登字第2262004号、登记号：2017SR676720）
// 4）国家版权局登记名(简称)和权证号：MobileIMSDK-Web（证书号：软著登字第2262073号、登记号：2017SR676789）
// 5）国家版权局登记名(简称)和权证号：MobileIMSDK    （证书号：软著登字第1220581号、登记号：2016SR041964）
// * 著作权所有人：江顺/苏州网际时代信息科技有限公司
//
// 【违法或违规使用投诉和举报方式】：
// 联系邮件：jack.jiang@52im.net
// 联系微信：hellojackjiang
// 联系QQ号：413980957
// 授权说明：http://www.52im.net/thread-1115-1-1.html
// 官方社区：http://www.52im.net
package com.x52im.rainbowchat.im.impl;

import io.netty.channel.Channel;

import java.util.Vector;
import java.util.concurrent.ConcurrentMap;

import net.x52im.mobileimsdk.server.protocal.Protocal;

import com.eva.epc.common.util.CommonUtils;
import com.eva.framework.dbpool.DBDepend;
import com.eva.framework.utils.LoggerFactory;
import com.x52im.rainbowchat.MyControllerJSON;
import com.x52im.rainbowchat.common.dto.cnst.OperateLog;
import com.x52im.rainbowchat.http.logic.LogicProcessor2;
import com.x52im.rainbowchat.http.logic.LogicUtils;
import com.x52im.rainbowchat.http.logic.dto.RosterElementEntity;
import com.x52im.rainbowchat.im.ChatServerEventListener;
import com.x52im.rainbowchat.im.ChatServerLauncher;
import com.x52im.rainbowchat.im.dto.CMDBody4AddFriendRequest;
import com.x52im.rainbowchat.im.dto.CMDBody4ProcessFriendRequest;
import com.x52im.rainbowchat.im.dto.MsgBody4Guest;
import com.x52im.rainbowchat.im.util.ChatServerUtils;
import com.x52im.rainbowchat.im.util.MessageHelper;
import com.x52im.rainbowchat.im.util.MessageHelper.SendResultObserver;
import com.x52im.rainbowchat.push.ios.APNSPushHelper;

/**
 * IM Server 服务器通用业务逻辑实现类（除群聊外的各种逻辑）.
 *
 * @author Jack Jiang(http://www.52im.net/space-uid-1.html)
 * @version 1.0
 */
public class ChatLogicManager {
    public ChatLogicManager() {
        //
    }

    // 处理临时聊天消息的最终投递
    public static boolean processMT42_OF_TEMP$CHAT$MSG_A$TO$SERVER(String fingerPrint, String dataContent) throws Exception {
        UsersStatusManager liverUsersMgr = ChatServerEventListener.getInstance().getLiverUsersMgr();

        // 解析临时聊天消息
        final MsgBody4Guest tempChatMsgFromClient = MessageHelper.pareseTempChatMsg_A$TO$SERVER_Message(dataContent);
//		tempChatMsgFromClient.setMsgTime(""+System.currentTimeMillis());
        // 结果正常解析出来了
        if (tempChatMsgFromClient != null) {
            // 目标用户是否在线（准确地说是否在线于APP的IM服务端）
            String destUser_id = tempChatMsgFromClient.getT();
            final boolean isDestFriendOnline = liverUsersMgr.isOnline(destUser_id);

            // 指令发送结果观察者（因为通信底层netty是异步通知数据发送结果的）
            SendResultObserver sendResultObserver = new SendResultObserver() {
                @Override
                public void update(boolean code, Protocal p) {
                    // 返回值true：对于应用层逻辑来说，无论是在线发送成功，或者是跨服桥接成功，都算是发送成功
                    if (code) {
                        LoggerFactory.getLog().debug("[RBChat]服务器通知由uid=" + tempChatMsgFromClient.getF()
                                + "发起的临时聊天消息，已经成功发给在线好友uid=" + tempChatMsgFromClient.getT() + ".");

                        // 说明：满足此条件，表示本地不在线但处于与Web互通模式的情况下，消息却发送成功，也需要进行App的push通知，
                        //       因为前提是app端已不在线，进行push是合情合理的（否则桥接成功的情况下，app这边即使不在线也永远收
                        //       不到app的push通知，除非web那端也要实现不在线时的app的push通知。
                        // 备忘：此处的push逻辑是为了省去web那端不在线时的app push通知逻辑，日后支持全功能集群后，就不需
                        //       要这个逻辑了，因为包括web集群实例在内，都将是对等的且必须支持app 的push逻辑——所以没必要以下代码。
                        if (!isDestFriendOnline && ChatServerLauncher.bridgeEnabled) {
                            // 尝试进行app端的push通知
                            OfflineMessageManager.processOfflineMessage(p, true); // 注意此参数为true！
                        }
                    }
                    // 返回值false：实时或桥接发送没有成功
                    else {
                        LoggerFactory.getLog().warn("[RBChat]服务器实时或桥接通知由uid=" + tempChatMsgFromClient.getF()
                                + "发起的临时聊天消息没有成功（目标uid=" + tempChatMsgFromClient.getT() + "），马上考虑做离线处理！");

                        // 将该条临时聊天消息插入到数据库中（以备下次接收方B上线后拿到）
                        OfflineMessageManager.processOfflineMessage(p, false);
                        ;
                    }
                }
            };

            // 将临时聊天消息实时转发给本次消息接收者（如果用户连在本APPP实例则直发，否则开启与web互通时将桥接发）
            MessageHelper.sendTempChatMsg_SERVER$TO$B_Message(destUser_id, tempChatMsgFromClient, fingerPrint, sendResultObserver);

            /** 20190328 by Jack Jiang：按照目前的设计原则，为了减化架构和处理的复杂性，服务端主动发起
             的S2C类型消息或指令一律由发送者处理（详见设计备忘），切记！ */
            // ** 【用户消息收集】收集用户的聊天消息，以供后台进行用户行为分析，
            MessageRecordManager.colectChattingMsgAsync(dataContent, fingerPrint);
        }

        return true;
    }

    /**
     * 处理发起好友请求指令的投递.
     *
     * @param addFriendMetaFromClient 添加好友请求原数据（客户端A发过来的）
     * @param liverUsersMgr
     * @return
     * @throws Exception
     */
    public static boolean processMT05_OF_ADD_FRIEND_REQUEST_A$TO$SERVER(final CMDBody4AddFriendRequest addFriendMetaFromClient) throws Exception {
        UsersStatusManager liverUsersMgr = ChatServerEventListener.getInstance().getLiverUsersMgr();

        // 结果正常解析出来了
        if (addFriendMetaFromClient != null) {
            // ** 合法性检查（同一个好友不能试图插2次到数据库中，虽然肯定插不进去）
            if (LogicProcessor2.db.queryData(
                    "select 1 from missu_roster where user_uid=" + addFriendMetaFromClient.getLocalUserUid()
                            + " and friend_user_uid=" + addFriendMetaFromClient.getFriendUserUid()).size() > 0) {
                String responseForError = "Sorry, ID:"
                        + addFriendMetaFromClient.getFriendUserUid() + " has been your lover, dosn't need add again.";
                // 把错误信息反馈给加好友请求者A
                MessageHelper.sendAddFriendRequestResponse_for$error_server$to$aMessage(addFriendMetaFromClient.getLocalUserUid(), responseForError, null);
                return false;
            }

            // ** 查询该人员的允许最大好友数、当前好友总数并判断是否超出最大好友数
            String[] friendCountQueryResult = LogicUtils.queryUserFriendCount(
                    addFriendMetaFromClient.getLocalUserUid());
            if (friendCountQueryResult != null && friendCountQueryResult.length >= 2) {
                int maxFriend = CommonUtils.getIntValue(friendCountQueryResult[0]);
                int curentFriend = CommonUtils.getIntValue(friendCountQueryResult[1]);
                LoggerFactory.getLog().debug("[RBChat]【发出】" + addFriendMetaFromClient.getLocalUserUid()
                        + "：最大好友数=" + maxFriend + "、当前总数=" + curentFriend);

//				// 合法性检查（一个人只能有一个好友），在此作代码检查以便给用户友好提示，实际上db里有唯一索引（绝不会存一人多个好友）
//				if(LogicProcessor.db.queryData(
//						"select 1 from missu_roster where user_uid="+addFriendMetaFromClient.getLocalUserUid()).size() > 0)
                // 2014-01-09日（RBChat2.0以后版本）起：当当前好友总数>=最大允许可友总数时则不允许再加好友了
                if (curentFriend >= maxFriend) {
                    String responseForError = "Sorry, you can only have " + maxFriend + " lovers, ID:"
                            + addFriendMetaFromClient.getFriendUserUid() + " add Failed.";
                    // 把错误信息反馈给加好友请求者A
                    MessageHelper.sendAddFriendRequestResponse_for$error_server$to$aMessage(addFriendMetaFromClient.getLocalUserUid(), responseForError, null);
                    return false;
                }
            } else {
                LoggerFactory.getLog().warn("[RBChat]查询"
                        + addFriendMetaFromClient.getLocalUserUid()
                        + "的最大好友数、当前总数结果有问题："
                        + "friendCountQueryResult==null 或 friendCountQueryResult.length < 2 !");
            }

            // ** 将加好友请求插入到数据库表中（以备被请求者处理：同意或忽略）
            try {
                // 如果对该好友的加好友请求对方还没有处理，则只需要更新邀请信息的最新时间即可（不需要重复插数据库了）
                if (LogicProcessor2.db.queryData("select 1 from missu_friendreq " +
                        "where user_uid=" + addFriendMetaFromClient.getLocalUserUid()
                        + " and be_user_uid=" + addFriendMetaFromClient.getFriendUserUid()).size() > 0) {
                    LoggerFactory.getLog().debug("[RBChat]由uid=" + addFriendMetaFromClient.getLocalUserUid()
                            + "发起的加好友uid=" + addFriendMetaFromClient.getFriendUserUid()
                            + "的请求，已经存在，更新最近请求时间即可.");

                    LogicProcessor2.db.update("update missu_friendreq set be_time="
                                    + DBDepend.getDefaultDatetimeFunc() + ", be_desc=?, be_timestamp=?" +
                                    " where user_uid=? and be_user_uid=?"
                            , new Object[]{addFriendMetaFromClient.getDesc()
                                    , "" + System.currentTimeMillis() // GMT+0的时间戳
                                    , addFriendMetaFromClient.getLocalUserUid()
                                    , addFriendMetaFromClient.getFriendUserUid()}, true);
                }
                // 否则是首次加该发友，插入一条邀请到数据库（此种情况是防止对方不在线，下次上线了还能来处理的情况）
                else {
                    LogicProcessor2.db.update("insert into missu_friendreq(" +
                                    "user_uid,be_user_uid,be_desc,bt_ip,be_time,be_timestamp) " +
                                    "values(?,?,?,?," + DBDepend.getDefaultDatetimeFunc() + ",?)"
                            , new Object[]{addFriendMetaFromClient.getLocalUserUid()
                                    , addFriendMetaFromClient.getFriendUserUid()
                                    , addFriendMetaFromClient.getDesc()
                                    , "[ip地址目前无法记录]"
                                    , "" + System.currentTimeMillis() // GMT+0的时间戳
                            }, true);
                }

                // 目标用户是否在线
                String destUser_id = addFriendMetaFromClient.getFriendUserUid();
                final boolean isDestFriendOnline = liverUsersMgr.isOnline(destUser_id);

                RosterElementEntity ree = LogicUtils.queryUserInfoByUID(addFriendMetaFromClient.getLocalUserUid(), null);
                // 自20140213 RBChat2.2（含）以后ex1作为服务端转发加好友请求（给B）时存放验证说明文本使用（包括离线消息）
                ree.setEx1(addFriendMetaFromClient.getDesc());

                // 指令发送结果观察者（因为通信底层netty是异步通知数据发送结果的）
                SendResultObserver sendResultObserver = new SendResultObserver() {
                    @Override
                    public void update(boolean code, Protocal p) {
                        // 返回值true：对于应用层逻辑来说，无论是在线发送成功，或者是跨服桥接成功，都算是发送成功
                        if (code) {
                            LoggerFactory.getLog().debug("[RBChat]服务器通知由uid=" + addFriendMetaFromClient.getLocalUserUid()
                                    + "发起的加好友请求，已经成功发给在线好友uid=" + addFriendMetaFromClient.getFriendUserUid() + ".");

                            // 说明：满足此条件，表示本地不在线但处于与Web互通模式的情况下，消息却发送成功，也需要进行App的push通知，
                            //       因为前提是app端已不在线，进行push是合情合理的（否则桥接成功的情况下，app这边即使不在线也永远收
                            //       不到app的push通知，除非web那端也要实现不在线时的app的push通知。
                            // 备忘：此处的push逻辑是为了省去web那端不在线时的app push通知逻辑，日后支持全功能集群后，就不需
                            //       要这个逻辑了，因为包括web集群实例在内，都将是对等的且必须支持app 的push逻辑——所以没必要以下代码。
                            if (!isDestFriendOnline && ChatServerLauncher.bridgeEnabled) {
                                //## 尝试向ios设备进行APNs离线消息推送（推送是基于netty+HTTP/2实现，因而是异步的不会阻塞余下代码执行）
                                APNSPushHelper.tryPushAddFriendProcess_request(addFriendMetaFromClient.getFriendUserUid()
                                        , addFriendMetaFromClient.getLocalUserUid());
                            }

//							return true;
                        } else {
                            LoggerFactory.getLog().warn("[RBChat]服务器实时或桥接通知由uid=" + addFriendMetaFromClient.getLocalUserUid()
                                    + "发起的加好友请求没有成功（目标uid=" + addFriendMetaFromClient.getFriendUserUid() + "），因离线和重试" +
                                    "机制的存在，本条提示可忽略。");

                            //## 尝试向ios设备进行APNs离线消息推送（推送是基于netty+HTTP/2实现，因而是异步的不会阻塞余下代码执行）
                            APNSPushHelper.tryPushAddFriendProcess_request(addFriendMetaFromClient.getFriendUserUid()
                                    , addFriendMetaFromClient.getLocalUserUid());

//							return false;
                        }
                    }
                };

                // 发送加好友消息通知
                MessageHelper.sendAddFriendRequestInfo_server$to$bMessage(destUser_id, ree, sendResultObserver);
                return false; // 返回值当前暂时没有作用
            } catch (Exception e) {
                String responseForError = "Add lover (id:" + addFriendMetaFromClient.getFriendUserUid()
                        + ") failed, reason is " + e.getMessage();
                // 把错误信息反馈给加好友请求者A
                MessageHelper.sendAddFriendRequestResponse_for$error_server$to$aMessage(addFriendMetaFromClient.getLocalUserUid(), responseForError, null);
                return false;
            }
        }

        return true;
    }

    /**
     * 处理好友请求(同意)指令的投递。
     *
     * @param processFriendMetaFromClient 被添加者同意加好友请求原数据（客户端A发过来的）
     * @param liverUsersMgr
     * @return
     * @throws Exception
     */
    public static boolean processMT08_OF_PROCESS_ADD$FRIEND$REQ_B$TO$SERVER_AGREE(
            final CMDBody4ProcessFriendRequest processFriendMetaFromClient) throws Exception {
//		// 解析被添加者同意加好友请求原数据（客户端A发过来的）
//		CMDBody4ProcessFriendRequest processFriendMetaFromClient =
//				MessageHelper.pareseProcessAdd$Friend$Req_B$To$Server_AGREEMessage(dataContent);

        UsersStatusManager liverUsersMgr = ChatServerEventListener.getInstance().getLiverUsersMgr();

        // 结果正常解析出来了
        if (processFriendMetaFromClient != null) {
            // ** 查询该人员的允许最大好友数、当前好友总数并判断是否超出最大好友数
            String[] friendCountQueryResult = LogicUtils.queryUserFriendCount(
                    processFriendMetaFromClient.getLocalUserUid());
            if (friendCountQueryResult != null && friendCountQueryResult.length >= 2) {
                int maxFriend = CommonUtils.getIntValue(friendCountQueryResult[0]);
                int curentFriend = CommonUtils.getIntValue(friendCountQueryResult[1]);
                LoggerFactory.getLog().debug("[RBChat]【接收】" + processFriendMetaFromClient.getLocalUserUid()
                        + "：最大好友数=" + maxFriend + "、当前总数=" + curentFriend);

                // 2014-01-09日（RBChat2.0以后版本）起：当当前好友总数>=最大允许可友总数时则不允许再加好友了
                if (curentFriend >= maxFriend) {
                    String local_user_id = processFriendMetaFromClient.getLocalUserUid();
                    // 如果本地用户在线的话，把错误信息发回给他
                    if (!RosterElementEntity.DEFAULT_INVALID_UID_VALUE.equals(local_user_id)) {
                        String responseForError = "Sorry, you can only have " + maxFriend + " lovers, ID:"
                                + processFriendMetaFromClient.getSrcUserUid() + " add Failed.";
                        // 把错误信息反馈给加好友请求者A
                        MessageHelper.sendAddFriendRequestResponse_for$error_server$to$aMessage(local_user_id, responseForError, null);
                    }
                    return false;
                }
            }

            //**××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× 准备好更新数据的SQL语句
            Vector sqls = new Vector(2);
            Vector values = new Vector(2);
            // 不存在就插入，否则忽略
            if (LogicProcessor2.db.queryData("select 1 from missu_roster where user_uid=" +
                    processFriendMetaFromClient.getLocalUserUid() + " and friend_user_uid="
                    + processFriendMetaFromClient.getSrcUserUid()).size() <= 0) {
                // 插入B的好友记录（好友是A）
                sqls.add("INSERT INTO missu_roster(user_uid,friend_user_uid,add_time) " +
                        "VALUES(?,?," + DBDepend.getDefaultDatetimeFunc() + ")");
                values.add(new Object[]{processFriendMetaFromClient.getLocalUserUid()
                        , processFriendMetaFromClient.getSrcUserUid()});
            }

            // 不存在就插入，否则忽略
            if (LogicProcessor2.db.queryData("select 1 from missu_roster where user_uid=" +
                    processFriendMetaFromClient.getSrcUserUid() + " and friend_user_uid=" + processFriendMetaFromClient.getLocalUserUid()).size() <= 0) {
                // 插入A的好友记录（好友是B）
                sqls.add("INSERT INTO missu_roster(user_uid,friend_user_uid,add_time) " +
                        "VALUES(?,?," + DBDepend.getDefaultDatetimeFunc() + ")");
                values.add(new Object[]{processFriendMetaFromClient.getSrcUserUid()
                        , processFriendMetaFromClient.getLocalUserUid()});
            }

            // 存在就删除
            if (LogicProcessor2.db.queryData("select 1 from missu_friendreq where user_uid=" +
                    processFriendMetaFromClient.getSrcUserUid() + " and be_user_uid=" + processFriendMetaFromClient.getLocalUserUid()).size() > 0) {
                // 删除A发过来（给B）的加好友请求信息
                sqls.add("DELETE FROM missu_friendreq where user_uid=? and be_user_uid=?");
                values.add(new Object[]{processFriendMetaFromClient.getSrcUserUid()
                        , processFriendMetaFromClient.getLocalUserUid()});
            }

            // 存在就删除
            if (LogicProcessor2.db.queryData("select 1 from missu_friendreq where user_uid=" +
                    processFriendMetaFromClient.getLocalUserUid() + " and be_user_uid=" + processFriendMetaFromClient.getSrcUserUid()).size() > 0) {
                // 删除B发给A的加好友请求信息（有可能会有这种情况，反正他们肯定成为好友了）
                // B发给A的好友请求，可能是曾今B也主动向A发过好友请求
                sqls.add("DELETE FROM missu_friendreq where user_uid=? and be_user_uid=?");
                values.add(new Object[]{processFriendMetaFromClient.getLocalUserUid()
                        , processFriendMetaFromClient.getSrcUserUid()});
            }

            //**××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× 提交到数据库中
            boolean sucess = false;
            String responseForError = null;
            try {
                LogicProcessor2.db.update(sqls, values, true);
                sucess = true;
            } catch (Exception e) {
                responseForError = "Prompt: Accept id:"
                        + processFriendMetaFromClient.getSrcUserUid() + "'s request for add lover failed, reaseon is " + e.getMessage();
            }

            //**××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× 处理成功， 通知双
            // 		方客户端（如果在线的话，不在线当然就不需要通知了，下次登陆即可载入好友列表）
            String localUser_id = null;//RosterElementEntity.DEFAULT_INVALID_UID_VALUE;
            if (sucess) {
                //** 尝试取出B用户和原A用户的在线user_id =======================================================
                final String srcUser_id = processFriendMetaFromClient.getSrcUserUid();
                localUser_id = processFriendMetaFromClient.getLocalUserUid();

                // A用户是否在线
                final boolean srcUserOnline = liverUsersMgr.isOnline(srcUser_id);
                // B用户是否在线
                boolean localUserOnline = liverUsersMgr.isOnline(localUser_id);

                // 指令发送结果观察者（因为通信底层netty是异步通知数据发送结果的）
                SendResultObserver sendResultObserverA = new SendResultObserver() {
                    @Override
                    public void update(boolean code, Protocal p) {
                        // 返回值true：对于应用层逻辑来说，无论是在线发送成功，或者是跨服桥接成功，都算是发送成功
                        if (code) {
                            // 说明：满足此条件，表示本地不在线但处于与Web互通模式的情况下，消息却发送成功，也需要进行App的push通知，
                            //       因为前提是app端已不在线，进行push是合情合理的（否则桥接成功的情况下，app这边即使不在线也永远收
                            //       不到app的push通知，除非web那端也要实现不在线时的app的push通知。
                            // 备忘：此处的push逻辑是为了省去web那端不在线时的app push通知逻辑，日后支持全功能集群后，就不需
                            //       要这个逻辑了，因为包括web集群实例在内，都将是对等的且必须支持app 的push逻辑——所以没必要以下代码。
                            if (!srcUserOnline && ChatServerLauncher.bridgeEnabled) {
                                //## 尝试向ios设备进行APNs离线消息推送（推送是基于netty+HTTP/2实现，因而是异步的不会阻塞余下代码执行）
                                APNSPushHelper.tryPushAddFriendProcess_accept(srcUser_id, processFriendMetaFromClient.getLocalUserNickName());
                            }
                        }
                        // 尝试离线通知好友请求发起者：他的加好友请求被同意了！
                        else {
                            //## 尝试向ios设备进行APNs离线消息推送（推送是基于netty+HTTP/2实现，因而是异步的不会阻塞余下代码执行）
                            APNSPushHelper.tryPushAddFriendProcess_accept(srcUser_id, processFriendMetaFromClient.getLocalUserNickName());
                        }
                    }
                };
                // 将好友添加成功的消息尝试实时发送给A用户（原加好友的发起人），不在线则会则在
                // 下次上线重新加载好友列表，所以不需要像普通消息一样的离线处理机制
                // -> 将B用户的个人信息发给A：
                MessageHelper.sendProcessAdd$Friend$Req_friend$Info$Server$To$ClientMessage(srcUser_id
                        , LogicUtils.queryUserInfoByUID(processFriendMetaFromClient.getLocalUserUid(), null).setLiveStatus(localUserOnline ? 1 : 0), sendResultObserverA);

                // 指令发送结果观察者（因为通信底层netty是异步通知数据发送结果的）
                SendResultObserver sendResultObserver = new SendResultObserver() {
                    @Override
                    public void update(boolean code, Protocal p) {
                        if (code) {
                            // 什么也不需要做！
                        } else {
                            // 好友请求的处理者就是被加者，因而不存在也不需要通过App的Push来即时通知本次请求的处理者（否则体验也太怪了）！
                        }
                    }
                };
                // 将好友添加成功的消息尝试实时发送给B用户（本次处理好友请求的发起方）
                // -> 将A用户的信人信息发给B（也就是发给本次好友请求的处理者）：
                MessageHelper.sendProcessAdd$Friend$Req_friend$Info$Server$To$ClientMessage(localUser_id
                        , LogicUtils.queryUserInfoByUID(processFriendMetaFromClient.getSrcUserUid(), null).setLiveStatus(srcUserOnline ? 1 : 0), sendResultObserver);

            }
            //** 尝试将出错信息发回给处理加好友请求的B
            else {
                // B用户在线（本次处理好友请求的发起方）
                if (liverUsersMgr.isOnline(localUser_id)) {
                    // 将出错信息及时反馈给B（本次处理的发起者）
                    MessageHelper.sendProcessAddFriendReq_response$For$Rrror_server$to$bMessage(localUser_id, responseForError, null);
                }
            }
        }

        return true;
    }

    /**
     * 处理好友请求(拒绝)指令的投递。
     *
     * @param processFriendMetaFromClient 被添加者拒绝加好友请求原数据（客户端A发过来的）
     * @param liverUsersMgr
     * @throws Exception
     */
    public static void processMT09_OF_PROCESS_ADD$FRIEND$REQ_B$TO$SERVER_REJECT(
            final CMDBody4ProcessFriendRequest processFriendMetaFromClient) throws Exception {
//		// 解析被添加者拒绝加好友请求原数据（客户端A发过来的）
//		CMDBody4ProcessFriendRequest processFriendMetaFromClient =
//				MessageHelper.pareseProcessAdd$Friend$Req_B$To$Server_REJECTMessage(dataContent);

        UsersStatusManager liverUsersMgr = ChatServerEventListener.getInstance().getLiverUsersMgr();

        // 结果正常解析出来了
        if (processFriendMetaFromClient != null) {
            //**××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× 准备好更新数据的SQL语句
            Vector sqls = new Vector(2);
            Vector values = new Vector(2);

            // 存在就删除
            if (LogicProcessor2.db.queryData("select 1 from missu_friendreq where user_uid=" +
                    processFriendMetaFromClient.getSrcUserUid() + " and be_user_uid=" + processFriendMetaFromClient.getLocalUserUid()).size() > 0) {
                // 删除A发过来（给B）的加好友请求信息（B如果发给A的就不用像“同意”里
                // 的处理一样——同时删除了只管删除A曾发过来的请求即可）
                sqls.add("DELETE FROM missu_friendreq where user_uid=? and be_user_uid=?");
                values.add(new Object[]{processFriendMetaFromClient.getSrcUserUid()
                        , processFriendMetaFromClient.getLocalUserUid()});
            }

            //**××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× 提交到数据库中
            boolean sucess = false;
            String responseForError = null;
            try {
                LogicProcessor2.db.update(sqls, values, true);
                sucess = true;
            } catch (Exception e) {
                responseForError = "Prompt: Reject id:"
                        + processFriendMetaFromClient.getSrcUserUid() + " for add lover failed, reason is " + e.getMessage();
            }

            //**××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× 处理成功， 通知被拒
            // 绝者的客户端（如果在线的话，不在线当然就不需要通知了，下次登陆即可载入数据库中存放的提示信息即可）
            String localUser_id = RosterElementEntity.DEFAULT_INVALID_UID_VALUE;
            if (sucess) {
                //** 尝试取出B用户和原A用户的在线user_id =======================================================
                String srcUser_id = processFriendMetaFromClient.getSrcUserUid();
                localUser_id = processFriendMetaFromClient.getLocalUserUid();

                // A用户是否在线
                final boolean srcUserOnline = liverUsersMgr.isOnline(srcUser_id);

                // 指令发送结果观察者（因为通信底层netty是异步通知数据发送结果的）
                SendResultObserver sendResultObserver = new SendResultObserver() {
                    @Override
                    public void update(boolean code, Protocal p) {
                        // 返回值true：对于应用层逻辑来说，无论是在线发送成功，或者是跨服桥接成功，都算是发送成功
                        if (code) {
                            // 说明：满足此条件，表示本地不在线但处于与Web互通模式的情况下，消息却发送成功，也需要进行App的push通知，
                            //       因为前提是app端已不在线，进行push是合情合理的（否则桥接成功的情况下，app这边即使不在线也永远收
                            //       不到app的push通知，除非web那端也要实现不在线时的app的push通知。
                            // 备忘：此处的push逻辑是为了省去web那端不在线时的app push通知逻辑，日后支持全功能集群后，就不需
                            //       要这个逻辑了，因为包括web集群实例在内，都将是对等的且必须支持app 的push逻辑——所以没必要以下代码。
                            if (!srcUserOnline && ChatServerLauncher.bridgeEnabled) {
                                //## 尝试向ios设备进行APNs离线消息推送（推送是基于netty+HTTP/2实现，因而是异步的不会阻塞余下代码执行）
                                APNSPushHelper.tryPushAddFriendProcess_reject(processFriendMetaFromClient.getLocalUserNickName()
                                        , processFriendMetaFromClient.getSrcUserUid());
                            }
                        } else {
                            //** 备忘：目前拒绝好友请求没有做离线处理，因为从产品的角度来说，“拒绝”是种不好的体验，
                            //**       目前可以采取静默处理（也就是不离线处理），一是技术上也简单，二是用户体验上
                            //**       也更人性化，必竟对于被拒者来说，没有看到通过，那就有可能被拒或对方没看到，
                            //**       从使用心理上来说，如果真的想要加对方，大不了再加一次或再加几次呗！

//							// 将B的拒绝信息存到数据库里，以便下次A登陆时，能看到提示消息，从而知
//							// 道他的加好友（加的B）的请求已经被拒绝了！
//							LogicProcessor2.db.update("insert into missu_other_alarms(" +
//									"user_uid,alarm_type,alarm_title,alarm_msg_content,alarm_time) values(?,?,?,?,?)"
//									, new Object[]{processFriendMetaFromClient.getSrcUserUid(), "2"
//										, "Add lover denied"
//										, "Request add lover "+processFriendMetaFromClient.getLocalUserNickName()+" has been reject."
//										, new SimpleDateFormat("yyyy-MM-dd HH:mm").format(new Date())}, true);

                            //## 尝试向ios设备进行APNs离线消息推送（推送是基于netty+HTTP/2实现，因而是异步的不会阻塞余下代码执行）
                            APNSPushHelper.tryPushAddFriendProcess_reject(processFriendMetaFromClient.getLocalUserNickName()
                                    , processFriendMetaFromClient.getSrcUserUid());
                        }
                    }
                };

                // A用户在线（原加好友的发起人）
                // --> 将拒绝者的个人信息发给A（以便及时告之他，加好友被拒了！）：
                MessageHelper.sendProcessAdd$Friend$Req_SERVER$TO$A_REJECT_RESULTMessage(srcUser_id
                        , LogicUtils.queryUserInfoByUID(processFriendMetaFromClient.getLocalUserUid(), null), sendResultObserver);
            }
            //** 尝试将出错信息发回给处理加好友请求的B
            else {
                // B用户在线（本次处理好友请求的发起方）
                if (!RosterElementEntity.DEFAULT_INVALID_UID_VALUE.equals(localUser_id)) {
                    // 将出错信息及时反馈给B（本次处理的发起者）
                    MessageHelper.sendProcessAddFriendReq_response$For$Rrror_server$to$bMessage(localUser_id, responseForError, null);
                }
            }
        }
    }

    public static void processUserLoginAction(final String userId, String extra, Channel session) {
        UsersStatusManager liverUsersMgr = ChatServerEventListener.getInstance().getLiverUsersMgr();

        // 将该用户加入到在线用户列表
        liverUsersMgr.userOnline(userId);
        // TODO 用于debug时，生产时可以去掉哦！
        liverUsersMgr.printLiveUsers();

        // ** 用户登陆成功后由服务端通知他的好友，他上线了，此时就把它的user_id传到服务端了！
        // 在线用户列表
        ConcurrentMap<String, Channel> liveUsers =
                ChatServerEventListener.getInstance().getLiverUsersMgr().getLiveUsers();
        try {
            // 该用户的好友列表
            Vector<Vector> roster = ChatServerUtils.getRosterByUid(userId);

            // 遍历好友列表，看看它有哪些好友是在线的
            for (Vector friendRow : roster) {
                // 好友uid
                final String friend_user_uid = (String) friendRow.get(0);
                final String friend_user_mail = (String) friendRow.get(1);
//				// 自已的uid（顺便取出来备用）
//				String user_uid = (String)friendRow.get(2);

                // FIXME: 备忘-> 因无法目前无全功能集群支持，无法取得所有im接入层的在线列表，所以
                //             当开通与web端互通时，就无法将上、下线通知给跟机器端了。不过，在线状态
                //             这种很难维护的东西，可以考虑像微信一样，在以后的版本中逐渐去掉，实际上来说
                //             ，用处也不是很大，只是传统IM（比如QQ）时代遗留下来的习惯而已！

                // 遍历在线好友列表，看看该好友是否在线
                if (liveUsers.containsKey(friend_user_uid)) {
                    // 指令发送结果观察者（因为通信底层netty是异步通知数据发送结果的）
                    SendResultObserver sendResultObserver = new SendResultObserver() {
                        @Override
                        public void update(boolean code, Protocal p) {
                            if (code)
                                LoggerFactory.getLog().debug("[RBChat]》登陆用户" + userId + "向它的好友"
                                        + friend_user_uid + "/" + friend_user_mail + "发送上线通知成功！");
                            else
                                LoggerFactory.getLog().debug("[RBChat]》登陆用户" + userId + "向它的好友"
                                        + friend_user_uid + "/" + friend_user_mail + "发送上线通知失败，错误code=" + code);
                        }
                    };
                    MessageHelper.sendOnlineMessage(friend_user_uid, userId, sendResultObserver);
                } else {
                    LoggerFactory.getLog().debug("[RBChat]》登陆用户" + userId + "的好友"
                            + friend_user_uid + "/" + friend_user_mail + "不在线，无需发送上线通知。");
                }
            }
        } catch (Exception e) {
            LoggerFactory.getLog().warn(e.getMessage(), e);
        }
    }

    public static void processUserLogoutAction(String userId, Object obj, Channel session) {
        // 在线用户列表
        UsersStatusManager liverUsersMgr = ChatServerEventListener.getInstance().getLiverUsersMgr();

        // ** 用户注销后由服务端通知他的好友，他下线了！
        // 下线的用户，必然之前已经在在线列表里了，因为通过它的user_id就能查到它登陆时的uid或者mail了
        if (userId != null) {
            try {
                // 该用户的好友列表
                Vector<Vector> roster = ChatServerUtils.getRosterByUid(userId);

                // 遍历好友列表，看看它有哪些好友是在线的
                for (Vector friendRow : roster) {
                    // 好友uid
                    final String friend_user_uid = (String) friendRow.get(0);
                    final String friend_user_mail = (String) friendRow.get(1);
                    // 自已的uid（顺便取出来备用）
                    final String user_uid = (String) friendRow.get(2);

                    // FIXME: 备忘-> 因无法目前无全功能集群支持，无法取得所有im接入层的在线列表，所以
                    //             当开通与web端互通时，就无法将上、下线通知给跟机器端了。不过，在线状态
                    //             这种很难维护的东西，可以考虑像微信一样，在以后的版本中逐渐去掉，实际上来说
                    //             ，用处也不是很大，只是传统IM（比如QQ）时代遗留下来的习惯而已！

                    // 看看该好友是否在线
//					if(liveUsers.containsKey(friend_user_uid))
                    if (liverUsersMgr.isOnline(friend_user_uid)) {
                        // 指令发送结果观察者（因为通信底层netty是异步通知数据发送结果的）
                        SendResultObserver sendResultObserver = new SendResultObserver() {
                            @Override
                            public void update(boolean code, Protocal p) {
                                if (code)
                                    LoggerFactory.getLog().info("[RBChat]《登陆用户" + user_uid + "向它的好友"
                                            + friend_user_uid + "/" + friend_user_mail + "发送下线通知成功！");
                                else
                                    LoggerFactory.getLog().warn("[RBChat]《登陆用户" + user_uid + "向它的好友"
                                            + friend_user_uid + "/" + friend_user_mail + "发送下线通知失败，错误code=" + code);
                            }
                        };
                        MessageHelper.sendOfflineMessage(friend_user_uid, user_uid, sendResultObserver);
                    } else {
                        LoggerFactory.getLog().debug("[RBChat]《登陆用户" + userId + "的好友"
                                + friend_user_uid + "/" + friend_user_mail + "不在线，无需发送下线通知【END】。");
                    }
                }
            } catch (Exception e) {
                LoggerFactory.getLog().warn(e.getMessage(), e);
            }
        } else {
            LoggerFactory.getLog().error("[RBChat]当前正在离线的用户" + userId + "为null？？？");
        }

        // 将该用户从在线用户列表中去掉
        liverUsersMgr.userOffline(userId);

        //增加退出系统日志
        OperateLog oprLog = new OperateLog(OperateLog.OPERATE_LOG_TYPE_LOGOUT_IM_SERVER
                , userId
                , "");
        try {
            LogicUtils.addOperateLog(oprLog);
        } catch (Exception e) {
            LoggerFactory.getLog().warn(e.getMessage(), e);
        }

        // TODO 用于debug时，生产时可以去掉哦！
        liverUsersMgr.printLiveUsers();
    }
}
